﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;

namespace Comdiv.UXmlDiff {
	/// <summary>
	/// working class that performs comparation of two XElements
	/// and returns set of Differences for both files
	/// </summary>
	public class XDiffExecutor {
		private XElement older;
		private XElement newer;

		public XDiffExecutor(string older, string newer)
		{
			this.older = XElement.Parse(older);
			this.newer = XElement.Parse(newer);
		}
		public XDiffExecutor(string older, XElement newer)
		{
			this.older = XElement.Parse(older);
			this.newer = newer;
		}
		public XDiffExecutor(XElement older, string newer)
		{
			this.older = older;
			this.newer = XElement.Parse(newer);
		}
		public XDiffExecutor(XElement older, XElement newer) {
			this.older = older;
			this.newer = newer;
		}

		private XDiffAnnotation[] result;
		
		public XDiffAnnotation[] Execute(bool safemode = false) {
			if(null!=result) {
				return result;
			}
			var r = new List<XDiffAnnotation>();
			var os = new XDiffSelector(older,safemode);
			var ns = new XDiffSelector(newer,safemode);
			var oc = os.GetNext();
			var nc = ns.GetNext();
			while (oc.Length>0 && nc.Length>0) {
				var length = Math.Min(oc.Length, nc.Length);
				int i = 0;
				XObject oe = null;
				XObject ne = null;
				XObject xoe = null;
				XObject xne = null;
				XDiffAnnotation oea = null;
				XDiffAnnotation nea = null;
				for (i = 0; i < length;i++ ) {
					oe = oc[i];
					ne = nc[i];
					if(oe.Diff().Id==ne.Diff().Id) {
						compare(r,oe, ne);
						continue;
					}
					xne = null;
					xoe = null;
					foreach (var _ne in nc) {
						if(_ne.Diff().Type!=XDiffAnnotationType.None)continue;
						if (_ne.Diff().Id == oe.Diff().Id) {
							xne = _ne;
							break;
						}
					}

					foreach (var _oe in oc)
					{
						if (_oe.Diff().Type != XDiffAnnotationType.None) continue;
						if (_oe.Diff().Id == ne.Diff().Id)
						{
							xoe = _oe;
							break;
						}
					}
					if(xne!=null) {
						compare(r,oe, xne);
					}
					if(xoe!=null) {
						compare(r,xoe, ne);
					}
					if(oe.Diff().Type==XDiffAnnotationType.None) {
						oe.Diff().Type = XDiffAnnotationType.Deleted;
						r.Add(oe.Diff());
					}
					if(ne.Diff().Type==XDiffAnnotationType.None) {
						ne.Diff().Type = XDiffAnnotationType.Created;
						r.Add(ne.Diff());
					}
				}
				if(oc.Length>length) {
					for (int k = length; k < oc.Length;k++ ) {
						if (oc[k].Diff().Type == XDiffAnnotationType.None) {
							oc[k].Diff().Type = XDiffAnnotationType.Deleted;
							r.Add(oc[k].Diff());
						}
					}
				}
				if (nc.Length > length)
				{
					for (int k = length; k < nc.Length; k++)
					{
						if (nc[k].Diff().Type == XDiffAnnotationType.None) {
							nc[k].Diff().Type = XDiffAnnotationType.Created;
							r.Add(nc[k].Diff());
						}

					}
				}
				oc = os.GetNext();
				nc =ns.GetNext();
			}
			if(oc.Length!=0) {
				foreach (var o in oc) {
					o.Diff().Type = XDiffAnnotationType.Deleted;
					r.Add(o.Diff());
				}
			}
			if (nc.Length != 0)
			{
				foreach (var o in nc)
				{
					o.Diff().Type = XDiffAnnotationType.Created;
					r.Add(o.Diff());
				}
			}
			return result = r.ToArray();
		}

		private void compare(List<XDiffAnnotation> r, XObject oe, XObject ne) {
			if(oe is XElement) {
				oe.Diff().Type = XDiffAnnotationType.Equal;
				ne.Diff().Type = XDiffAnnotationType.Equal;
				return;
			}
			if(oe is XAttribute) {
				var oa = oe as XAttribute;
				var na = ne as XAttribute;
				if(oa.Value==na.Value) {
					oe.Diff().Type = XDiffAnnotationType.Equal;
					ne.Diff().Type = XDiffAnnotationType.Equal;
					return;
				}else {
					oe.Diff().Type = XDiffAnnotationType.Changed;
					oe.Diff().OldValue = oa.Value;
					oe.Diff().NewValue = na.Value;
					ne.Diff().Type = XDiffAnnotationType.NewValue;
					ne.Diff().OldValue = oa.Value;
					ne.Diff().NewValue = na.Value;
					r.Add(oe.Diff());
					return;
				}
			}
			if (oe is XText)
			{
				var oa = oe as XText;
				var na = ne as XText;
				if (oa.Value == na.Value)
				{
					oe.Diff().Type = XDiffAnnotationType.Equal;
					ne.Diff().Type = XDiffAnnotationType.Equal;
					return;
				}
				else
				{
					oe.Diff().Type = XDiffAnnotationType.Changed;
					oe.Diff().OldValue = oa.Value;
					oe.Diff().NewValue = na.Value;
					ne.Diff().Type = XDiffAnnotationType.NewValue;
					ne.Diff().OldValue = oa.Value;
					ne.Diff().NewValue = na.Value;
					r.Add(oe.Diff());
					return;
				}
			}
		}
	}
}